import { EventEmitter } from 'events';
// @ts-ignore
import ms from 'ms';
import uuid from 'uuid';
import moment from 'moment/moment';
import 'moment/locale/zh-cn'; // JavaScript 日期处理类库 - 全局设置中文
import CryptoJS from 'crypto-js';

export type DiffRowType = 0 | 1 | 2 | 3;
export type DiffRow = {
  type: DiffRowType;
  key: string;
  children?: DiffRow[];
};

export interface messageOptions extends Record<string, any> {
  type?: 'success' | 'warning' | 'info' | 'error';
  message: string;
  duration?: number;
}

export interface notifyOptions extends Record<string, any> {
  type?: 'success' | 'warning' | 'info' | 'error';
  title?: string;
  message: string;
  duration?: number;
}

const event = new EventEmitter();

export default class {
  static event = event;
  static ms = ms;
  static uuid = uuid;
  static log = console.log;

  static _loadingInstance: any;

  /**
   * 判断数据类型
   * @param data 要获取类型的数据
   * @returns 返回数据类型字符串
   */
  static type(data: any): string {
    const _type = Object.prototype.toString;
    let typeName = _type.call(data);
    typeName = typeName.slice(8, -1).toLocaleLowerCase();
    return typeName;
  }

  /**
   * 判断一个变量是否是 Promise 对象
   * @param {any} variable - 需要判断的变量
   * @returns {boolean} - 如果变量是 Promise 对象，则返回 true；否则返回 false
   */
  static isPromise(variable: any) {
    return variable instanceof Promise;
  }

  /**
   * 对象深度克隆
   * @param data 要克隆的对象
   * @returns 返回一个克隆后的新对象
   */
  static deepClone<T = Record<string, any>>(data: T) {
    return JSON.parse(JSON.stringify(data)) as T;
  }

  /**
   * 获取指定范围内的随机整数
   * @param minNum 最小值 (默认：0)
   * @param maxNum 最大值 (默认：0)
   * @returns 随机整数
   */
  static randomNum(minNum = 0, maxNum = 0) {
    switch (arguments.length) {
      case 1:
        return Math.round(Math.random() * minNum);
      case 2:
        return Math.round(Math.random() * (maxNum - minNum) + minNum);
      default:
        return 0;
    }
  }

  /**
   * 将秒数转换为00:00的时间格式
   * @param total 总秒数
   * @returns 00:00格式的时间字符串
   */
  static durationFormat(total: number): string {
    // 计算分钟数并将其格式化为两位数
    const minutes = `${`00${Math.floor(total / 60)}`.slice(-2)}`;
    // 计算剩余秒数并将其格式化为两位数
    const seconds = `${`00${Math.floor(total % 60)}`.slice(-2)}`;
    // 将分钟数和秒数格式化为00:00的时间字符串并返回
    return `${minutes}:${seconds}`;
  }

  /**
   * 格式化价格，保留小数点后两位
   * @param price 价格
   * @returns 格式化后的价格字符串
   */
  static formatPrice(price: number): string {
    const formattedPrice = Math.round(price * 100) / 100;
    return formattedPrice.toFixed(2);
  }

  /**
   * 计算已使用金额与总金额的占比
   * @param totalAmount 总金额
   * @param usedAmount 已使用金额
   * @returns 占比（保留两位小数）
   */
  static calculatePercentage(totalAmount: number, usedAmount: number): number {
    // 如果总金额为0，则返回-100，表示无法计算占比
    if (totalAmount === 0) return -100;

    // 计算余额
    const remainingAmount = totalAmount - usedAmount;

    // 计算占比并保留两位小数
    const percentage = ((remainingAmount / totalAmount) * 100).toFixed(2);
    return Number(percentage);
  }

  /**
   * 异步延迟函数
   * @param delayTime 延迟时间，单位为毫秒，默认值为500
   * @returns Promise<void>
   */
  static async deferAsync(delayTime = 500): Promise<void> {
    return new Promise((resolve) => {
      setTimeout(resolve, delayTime);
    });
  }

  /**
   * 格式化标准时间
   * @param time 要格式化的时间，默认为当前时间
   * @param formatStr 格式化字符串，默认为'YYYY-MM-DD HH:mm:ss'
   * @returns 返回格式化后的时间字符串
   */
  static formatTime(time: moment.MomentInput = +new Date(), formatStr = 'YYYY-MM-DD HH:mm:ss') {
    return moment(time).format(formatStr);
  }

  /**
   * 格式化相对时间 -- 1 分钟前
   * @param time 要格式化的时间
   * @returns 返回格式化后的相对时间字符串
   */
  static formatTimeRelativity(val: moment.MomentInput) {
    return moment(val).fromNow();
  }

  /**
   * 格式化相对时间 -- 今天13:56
   * @param val 要格式化的时间
   * @returns 返回格式化后的相对时间字符串
   */
  static calendar(val: moment.MomentInput) {
    return moment(val).calendar();
  }

  /**
   * 通过 映射表达式(keys) 将旧对象中指定的键值映射到新对象中
   * @param keys 映射表达式，格式为 ['目标键:源键', ...]
   * @param oldItem 待映射对象
   * @returns 映射后生成新对象
   */
  static mapKeys(keys: string[], oldItem: Record<string, any>): Record<string, any>;
  /**
   * 通过 映射表达式(keys) 将旧对象中指定的键值映射到新对象中
   * @param keys 映射表达式，格式为 ['目标键:源键', ...]
   * @param newItem 目标对象
   * @param oldItem 待映射对象
   * @returns 映射后生成新对象
   */
  static mapKeys(keys: string[], newItem: Record<string, any>, oldItem: Record<string, any>): Record<string, any>;
  static mapKeys(keys: string[], newItem: Record<string, any>, oldItem?: Record<string, any>): Record<string, any> {
    let _newItem: Record<string, any>;
    let _oldItem: Record<string, any>;
    // 判断是否传入旧对象
    if (typeof oldItem === 'object') {
      _newItem = newItem;
      _oldItem = oldItem;
    } else {
      _newItem = {};
      _oldItem = newItem;
    }
    // 遍历要映射的键
    for (let index = 0; index < keys.length; index++) {
      // 将键根据冒号分割成 源键(_keyRaw) 和 目标键(_keyMap)
      const [_keyRaw, _keyMap = _keyRaw] = keys[index].split(':');
      const _keyMaps = _keyMap.split('.');
      let _value: any;
      if (_keyMaps.length) {
        _value = _keyMaps.reduce((accumulator, key) => {
          if (typeof accumulator?.[key] === 'undefined') return;
          return accumulator[key];
        }, _oldItem);
      } else {
        _value = _oldItem[_keyMap];
      }
      // 如果旧对象中目标键对应的值未定义，则跳过此次映射
      if (typeof _value === 'undefined') continue;
      // 将旧对象中目标键对应的值映射到新对象中源键对应的位置
      const _keyRaws = _keyRaw.split('.');
      _keyRaws.reduce((accumulator, key, index) => {
        if (_keyRaws.length === index + 1) {
          accumulator[key] = _value;
        } else {
          if (!(accumulator?.[key] && typeof accumulator[key] === 'object')) accumulator[key] = {};
        }
        return accumulator[key];
      }, _newItem);
    }
    // 返回映射后的新对象
    return _newItem;
  }

  /**
   * 将一个数组分成若干个子数组，每个子数组包含不超过指定长度的元素。
   * @param arr 需要被分组的数组
   * @param chunkSize 每个子数组的最大长度
   * @returns 分组后的新数组
   */
  static chunkArray<T>(arr: T[], chunkSize: number): T[][] {
    if (!Array.isArray(arr) || chunkSize <= 0) {
      return [];
    }

    // 如果原始数组是字符串类型，则将其转换为字符数组
    if (typeof arr === 'string') {
      arr = [...arr] as any[];
    }

    let index = 0;
    const newArray: T[][] = [];
    while (index < arr.length) {
      newArray.push(arr.slice(index, index + chunkSize));
      index += chunkSize;
    }
    return newArray;
  }

  /**
   * 属性名和注释的映射表类型
   */
  static formatData<T = Record<string, any>>(data: T, LABELS: Record<keyof T, string>): string {
    // 将对象序列化为字符串，并加上缩进
    const serialized = JSON.stringify(data, null, 2);
    // 遍历 LABELS 映射表，将属性名前加上相应的注释，并对齐属性值
    const formatted = Object.entries(LABELS).reduce((str, [propName, label]) => {
      const prefix = `// ${label} -- ${propName}`;
      const reg = new RegExp(`([ ]+)"${propName}": `);
      return str.replace(reg, (_$, _$1) => `${_$1}${prefix}\n${_$}`);
    }, serialized);
    return formatted;
  }

  /**
   * 加密方法
   * @param originalText  原始文本
   * @param key 密钥
   * @returns
   */
  static encrypt(originalText: string, key: string) {
    const ciphertext = CryptoJS.AES.encrypt(originalText, key).toString();
    return ciphertext;
  }

  /**
   * 解密方法
   * @param ciphertext 密文
   * @param key 密钥
   * @returns
   */
  static decrypt(ciphertext: string, key: string) {
    ciphertext = (ciphertext || '').trim();
    const bytes = CryptoJS.AES.decrypt(ciphertext, key);
    const originalText = bytes.toString(CryptoJS.enc.Utf8);
    return originalText;
  }

  /**
   * 对传入的字符串进行 MD5 哈希计算
   * @param str {string} 要进行哈希计算的字符串
   * @returns 哈希计算结果，以字符串形式返回
   */
  static md5(str: string) {
    return CryptoJS.MD5(str).toString();
  }

  /**
   * 对数据对象进行 MD5 签名
   * @param data 数据对象
   * @param key {string} 签名密钥，默认值为一段固定字符串
   * @returns 签名结果，以大写的 MD5 值的形式返回
   */
  static sign(data: Record<string, any>, key: string) {
    const arr = Object.keys(data).sort();
    return this.md5(`${key}${arr.map((k) => `${k}${data[k]}`).join('')}${key}`).toUpperCase();
  }

  /**
   * 生成带有签名和随机数的数据对象
   * @param data 待加密数据对象
   * @param key {string} 签名密钥
   * @returns 生成的带有签名和随机数的数据对象
   */
  static createSignData(data: Record<string, any> = {}, key: string) {
    data = {
      rr: Math.random().toString(36).slice(-10),
      tm: (+new Date()).toString().slice(0, 10),
      ...data
    };
    data.sign = this.sign(data, key);
    return data;
  }

  /**
   * @description 对象比较，返回差异
   * @param {object} oldVal 需要比较的对象A
   * @param {object} newVal 需要比较的对象B
   */
  static json_diff(oldVal: Record<string, any>, newVal: Record<string, any>, depth = 0): [string[], DiffRow[]] {
    let rows: DiffRow[] = [];
    const keys_old = Object.keys(oldVal);
    const keys_new = Object.keys(newVal);
    const keys_all = [...new Set([...keys_old, ...keys_new])];
    keys_all.forEach((key) => {
      const _isOld = keys_old.includes(key);
      const _isNew = keys_new.includes(key);
      let _valOld = oldVal[key];
      let _valNew = newVal[key];
      if (typeof _valOld === 'object') _valOld = JSON.stringify(_valOld);
      if (typeof _valNew === 'object') _valNew = JSON.stringify(_valNew);
      if (_isOld && _isNew) {
        let type: DiffRowType = 0;
        // 共有属性
        if (_valOld !== _valNew) type = 2; // 有改动的数据
        rows.push({ type, key });
      } else if (_isOld) {
        // 删除行
        rows.push({ type: 3, key });
      } else {
        // 新增行
        rows.push({ type: 1, key });
      }
    });
    const _sortTypes = [0, 2, 3, 1];
    rows.sort((a, b) => _sortTypes.indexOf(a.type) - _sortTypes.indexOf(b.type));
    rows = rows.filter((row) => row.type !== 0);
    const result: string[] = [];
    rows.forEach((row) => {
      const { type, key } = row;
      let _valOld = oldVal[key];
      let _valNew = newVal[key];
      if (typeof _valOld === 'object') _valOld = JSON.stringify(_valOld);
      if (typeof _valNew === 'object') _valNew = JSON.stringify(_valNew);
      let _rows: string[] = [];
      if (type === 2) {
        _rows.push(`${type} - [${key}] ${_valOld}`);
        _rows.push(`${type} + [${key}] ${_valNew}`);
      } else if (type === 1) {
        _rows.push(`${type} + [${key}] ${_valNew}`);
      } else if (type === 3) {
        _rows.push(`${type} - [${key}] ${_valOld}`);
      } else {
        _rows.push(`${type} + [${key}] ${_valNew}`);
      }
      const _strs = new Array(depth).fill('  ');
      _rows = _rows.map((str) => `${_strs.join('')}${str}`);
      if (type === 2 && typeof newVal[key] === 'object' && typeof oldVal[key] === 'object') {
        const [_result, rows] = this.json_diff(oldVal[key], newVal[key], depth + 1);
        row.children = rows;
        _rows = [];
        result.push(`${_strs.join('')}${type} +- ${key}`, ..._result);
      }
      result.push(..._rows);
    });
    return [result, rows];
  }

  // 初始随机的定时器
  static randomTimers(func: () => void, interval = 3000, immediately = false) {
    immediately && func();
    setTimeout(() => {
      func();
      setInterval(() => func(), interval);
    }, this.randomNum(interval));
  }

  /**
   * 防抖函数，用于延迟执行某个函数
   * @param func 要执行的函数
   * @param delay 延迟时间，默认为 500 毫秒
   * @returns 返回一个新的函数
   */
  static debounce<T extends (...args: any[]) => void>(func: T, delay = 500) {
    let _timerId: any = null;
    return function (...args: Parameters<T>) {
      clearTimeout(_timerId);
      _timerId = setTimeout(() => {
        func.apply(this, args);
      }, delay);
    };
  }

  /**
   * 节流函数，用于限制函数的执行频率
   * @param func 要执行的函数
   * @param delay 等待时间，默认为 500 毫秒
   * @returns 返回一个新的函数
   */
  static throttle<T extends (...args: any[]) => void>(func: T, delay = 500) {
    let _timerId: any = null;
    return function (...args: Parameters<T>) {
      if (_timerId) return;
      func.apply(this, args);
      _timerId = setTimeout(() => {
        _timerId = null;
      }, delay);
    };
  }

  // /**
  //  * 获取 jsonp 数据
  //  * @param funcName 函数名
  //  * @param jsonp jsonp 字符串
  //  * @returns JSON 数据
  //  */
  // static getCallbackJson(funcName: string, jsonp: string) {
  //   return eval(`(()=>{ function ${funcName}(json){return json;}; return ${jsonp.trim()} })()`);
  // }

  // 格式化url为https、http的形式  默认https
  static formatUrl(str: string, is?: boolean) {
    if (this.type(str) !== 'string') return str;
    str = str.replace(/^\/\//, is ? 'http://' : 'https://');
    return str;
  }

  static baseLoading(isLoading: boolean) {
    console.log('[baseLoading]', isLoading);
  }

  static message(options: string | messageOptions) {
    console.log('[message]', options);
  }

  static notify(options: string | notifyOptions) {
    console.log('[notify]', options);
  }
}
